"use strict";
require("./enhance");
const mongodb = require('mongodb');
const MongoClient = mongodb.MongoClient;
const ObjectID = mongodb.ObjectID;
const Long = mongodb.Long;
const co = require("co");
const rp = require('request-promise');
const Redis = require('ioredis');
const md5 = require("blueimp-md5");

const Room = require("./entity/Room.js");
const User = require("./entity/User.js");
const Card = require("./entity/Card.js");
const Action = require("./entity/Action.js");
const Game = require("./entity/Game.js");
const utils = require("./utils");
const g = require("./global.js");
const micro = require("./micro.js");
const majiang = require("./yuanjiangLogic.js");

const USER_DEFULT_POINT = 0;

const WX_ACCESSTOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token";
const WX_USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo";
const WX_APPID = "wx9d0a695aa496744f";
const WX_APPKEY = "86003db79369541acbdd012bcf79c9cf";

const BUY_MSG = "购买房卡请加代理微信 WX123456";
const MSG = "游戏充值请找客服。湖南麻将杜绝外挂，一切说有外挂的都是骗子，谨防上当受骗。湖南麻将仅仅是休闲游戏，请勿赌博";

const SALT = "g9ByuRar0vJkW8sc";

let sleep = function(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
};

module.exports = (() => {
    let db;
    let seneca = micro.getClient();
    let nextId;
    let redisClients;
    let sessionExpire;
    let checkSession;
    let getUserGameInfo;
    let roomClearTimerMap = new Map();
    let unsetUserRoom = {
        roomNumber: 0,
        point: 0,
        rollPoint1: 0,
        rollPoint2: 0,
        gamePoint: 0,
        roomDirection: 0,
        isBaoTing: 0,
        isReady: 0,
        fangpaoCount: 0,
        jiePaoCount: 0,
        zimoCount: 0,
        isAgreeDismiss: 0,
    };
    let saveRecord = co.wrap(function*(room) {
        let allUsers = yield db.collection(g.colls.users).find({
            roomNumber: room.number
        }).toArray();
        let time = new Date().getTime();
        if (room.currentGameIndex == 1) {
            let users = allUsers.map((user) => {
                return {
                    userName: user.nickname,
                    point: user.point
                };
            });
            let arr = [];
            for (let user of allUsers) {
                arr.push({
                    roomNumber: room.number,
                    userId: user.unionid,
                    time,
                    users
                });
            }
            yield db.collection(g.colls.records).insertMany(arr);
        }
        let users = allUsers.map((user) => {
            return {
                userName: user.nickname,
                point: user.gamePoint
            };
        });
        let users2 = allUsers.map((user) => {
            return {
                userName: user.nickname,
                point: user.point
            };
        });
        let arr = [];
        for (let user of allUsers) {
            let records = yield db.collection(g.colls.records).find({
                userId: user.unionid
            }).sort({
                _id: -1
            }).limit(1).toArray();
            let record = records[0];
            yield db.collection(g.colls.records).updateOne({
                _id: record._id
            }, {
                $set: {
                    users: users2
                }
            });
            arr.push({
                roomNumber: room.number,
                index: room.currentGameIndex,
                userId: user.unionid,
                time,
                users,
                recordId: record._id,
            });
        }
        yield db.collection(g.colls.recordDetails).insertMany(arr);
    });
    //释放某个房间
    let clearRoom = co.wrap(function*(roomNumber) {
        //清空房间所有用户房间号，积分，方向
        yield db.collection(g.colls.users).updateMany({
            roomNumber: roomNumber
        }, {
            $unset: unsetUserRoom
        });
        //清空房间所有的牌
        yield db.collection(g.colls.cards).deleteMany({
            roomNumber: roomNumber
        });
        //清空这个房间所有可做操作
        yield db.collection(g.colls.actions).deleteMany({
            roomNumber: roomNumber
        });
        //清空这个房间所有的杠的情况
        yield db.collection(g.colls.gangs).deleteMany({
            roomNumber: roomNumber
        });
        //清空games
        yield db.collection(g.colls.games).deleteMany({
            roomNumber: roomNumber
        });
        //清空过手信息
        yield db.collection(g.colls.guoShous).deleteMany({
            roomNumber: roomNumber
        });
        //删除这个房间
        yield db.collection(g.colls.rooms).deleteOne({
            number: roomNumber
        });
    });
    let userMoPai = co.wrap(function*(room, moPaiUser, /**可选**/ game, /**可选**/ allUsers, /**可选**/ remainCards, /**是否是杠后摸的牌**/ isGangPai) {
        if (!game) {
            game = yield db.collection(g.colls.games).findOne({
                _id: room.currentGameId
            });
        }
        if (!allUsers) {
            allUsers = yield db.collection(g.colls.users).find({
                roomNumber: room.number
            }).toArray();
        }
        if (!remainCards) {
            remainCards = yield db.collection(g.colls.cards).find({
                roomNumber: room.number,
                status: Card.STATUS_INIT,
            }).toArray();
        }
        //摸一张牌
        let r = yield db.collection(g.colls.cards).findOneAndUpdate({
            roomNumber: room.number,
            userId: {
                $exists: false
            }
        }, {
            $set: {
                userId: moPaiUser.unionid,
                roomNumber: room.number,
                status: Card.STATUS_ONHAND
            }
        }, {
            returnOriginal: false
        });
        let card = r.value;
        let notifies = [];
        if (!card) {
            //没有牌摸了，流局
            let allCards = yield db.collection(g.colls.cards).find({
                roomNumber: room.number
            }).toArray();
            let gangs = yield db.collection(g.colls.gangs).find({
                roomNumber: room.number
            }).toArray();
            for (let _user of allUsers) {
                let notify = {
                    to: _user.unionid,
                    method: "onOver",
                    data: {
                        cards: allCards,
                        gangs,
                    }
                };
                notifies.push(notify);
            }
            //把结果保存到game中
            yield db.collection(g.colls.games).updateOne({
                _id: game._id
            }, {
                $set: {
                    result: {
                        cards: allCards,
                        type: Game.Result.TYPE_LIUJU,
                        gangs,
                    }
                }
            });
            yield saveRecord(room);
        } else {
            //清空过手信息，如果用户已经报听，不能清空接炮的过手
            if (moPaiUser.isBaoTing) {
                let guoShous = yield db.collection(g.colls.guoShous).find({
                    userId: moPaiUser.unionid,
                }).toArray();
                let jiepaoGuoShouIds = guoShous.filter((guoshuo) => {
                    return guoShous.guoActions && guoshuo.contains(Action.ACTIONS_JIEPAO);
                }).map((guoshuo) => {
                    return guoshuo._id;
                });
                yield db.collection(g.colls.guoShous).deleteMany({
                    _id: {
                        $in: jiepaoGuoShouIds
                    }
                });
            } else {
                yield db.collection(g.colls.guoShous).deleteMany({
                    userId: moPaiUser.unionid
                });
            }
            //通知用户摸到牌，告诉用户可以做的操作
            let cards = yield db.collection(g.colls.cards).find({
                userId: moPaiUser.unionid
            }).toArray();
            let actions = [];
            let others = allUsers.filter((_user) => {
                return _user.unionid != moPaiUser.unionid;
            });
            let canZiMo = majiang[room.mode].canZiMo(cards);
            if (canZiMo) {
                actions.push({
                    userId: moPaiUser.unionid,
                    roomNumber: room.number,
                    gameId: game._id,
                    type: Action.ACTIONS_ZIMO,
                    status: Action.STATUS_WAIT,
                    isGangBao: isGangPai,
                    zimoCard: card
                });
            }
            let res = majiang[room.mode].canJiaGang(cards, card);
            if (!moPaiUser.isBaoTing) {
                actions.push(...res.actions);
            }
            res = majiang[room.mode].canAnGang(cards);
            actions.push(...res.actions);
            for (let action of actions) {
                action.userId = moPaiUser.unionid;
                action.roomNumber = room.number;
                action.gameId = game._id;
                action.status = Action.STATUS_WAIT;
            }
            //只有当不是海底的牌时，才能打牌
            if (remainCards.length > 1) {
                let daAction = {
                    userId: moPaiUser.unionid,
                    roomNumber: room.number,
                    gameId: game._id,
                    type: Action.ACTIONS_DA,
                    status: Action.STATUS_WAIT,
                };
                actions.push(daAction);
                for (let _user of others) {
                    notifies.push({
                        to: _user.unionid,
                        method: "onActions",
                        data: {
                            actions: [daAction]
                        }
                    });
                }
            } else {
                //海底不能打牌，所以要加入过的按钮
                if (actions.length) {
                    if (!canZiMo) {
                        actions.push({
                            type: Action.ACTIONS_GUO,
                            userId: moPaiUser.unionid,
                            roomNumber: room.number,
                            gameId: game._id,
                            status: Action.STATUS_WAIT,
                            isHaiDiGuo: true,
                            otherCard: card,
                        });
                    }
                } else {
                    room.direction = Room.nextDirection(room.direction);
                    let res = yield nextUserMoPai(room, game, allUsers, remainCards);
                    notifies.push(...res.notifies);
                }
            }
            if (actions.length) {
                yield db.collection(g.colls.actions).insertMany(actions);
            }
            notifies.push({
                to: moPaiUser.unionid,
                method: "onMoPai",
                data: {
                    card,
                    actions
                }
            });
            for (let other of others) {
                notifies.push({
                    to: other.unionid,
                    method: "onOtherMoPai",
                    data: {
                        moPaiUser: moPaiUser.unionid
                    }
                });
            }
        }
        return Promise.resolve({
            notifies: notifies
        });
    });
    //下一个用户摸牌
    let nextUserMoPai = co.wrap(function*(room, /**可选**/ game, /**可选，这个房间里的全部用户**/ allUsers, /**可选，房间剩余的牌**/ remainCards) {
        if (!game) {
            game = yield db.collection(g.colls.games).findOne({
                _id: room.currentGameId
            });
        }
        if (!allUsers) {
            allUsers = yield db.collection(g.colls.users).find({
                roomNumber: room.number
            }).toArray();
        }
        if (!remainCards) {
            remainCards = yield db.collection(g.colls.cards).find({
                roomNumber: room.number,
                status: Card.STATUS_INIT,
            }).toArray();
        }
        //房间到下一个方向
        let nextDirection = Room.nextDirection(room.direction);
        yield db.collection(g.colls.rooms).updateOne({
            number: room.number
        }, {
            $set: {
                direction: nextDirection
            }
        });
        let moPaiUser = (() => {
            for (let other of allUsers) {
                if (other.roomDirection == nextDirection) return other;
            }
        })();
        let result = yield userMoPai(room, moPaiUser, game, allUsers, remainCards);
        return Promise.resolve(result);
    });
    //加杠的操作
    let jiaGang = co.wrap(function*(user, allUsers, others, room, game, action) {
        let notifies = [];
        //把手中这个值的牌都改成加杠的牌
        yield db.collection(g.colls.cards).updateMany({
            userId: user.unionid,
            value: action.otherCard.value
        }, {
            $set: {
                status: Card.STATUS_JIAGANG
            }
        });
        yield db.collection(g.colls.gangs).insertOne({
            userId: user.unionid,
            roomNumber: room.number,
            otherId: action.otherCard.userId,
            type: Action.ACTIONS_JIAGANG
        });
        for (let _user of others) {
            notifies.push({
                to: _user.unionid,
                method: "onJiaGang",
                data: {
                    actions: [action]
                }
            });
        }
        let remainCards = yield db.collection(g.colls.cards).find({
            roomNumber: user.roomNumber,
            status: Card.STATUS_INIT
        }).toArray();
        if (remainCards.length <= 1) {
            //下一个人摸牌
            let res = yield nextUserMoPai(room, game, allUsers);
            notifies.push(...res.notifies);
        } else {
            //摸一张
            let res = yield userMoPai(room, user, game, allUsers, remainCards, /**是否是杠摸的牌**/ true);
            notifies.push(...res.notifies);
        }
        return {
            notifies: notifies
        };
    });
    //action的类型只能是接炮，明杠，碰，吃，过
    let handleActions = [Action.ACTIONS_JIEPAO, Action.ACTIONS_MINGGANG, Action.ACTIONS_PENG, Action.ACTIONS_CHI, Action.ACTIONS_GUO];
    let userZimo = co.wrap(function*(user, room, game, isTianHu, isGangBao, zimoCard) {
        let users = yield db.collection(g.colls.users).find({
            roomNumber: room.number
        }).toArray();
        let cards = yield db.collection(g.colls.cards).find({
            userId: user.unionid
        }).toArray();
        let remainCards = yield db.collection(g.colls.cards).find({
            roomNumber: user.roomNumber,
            status: Card.STATUS_INIT
        }).toArray();
        //返回所有的门子，赢的点数，扎鸟的牌
        let res = majiang[room.mode].zimo(cards, remainCards, room.birdCount, user.isBaoTing, isGangBao, room.maxBase, isTianHu, zimoCard);
        if (res.base == 0) {
            return Promise.reject({
                method: "zimo",
                errcode: 1,
                info: "没有满足胡牌的条件"
            });
        }
        let winGangs = yield db.collection(g.colls.gangs).find({
            userId: user.unionid
        }).toArray();
        for (let gang of winGangs) {
            if (gang.type == Action.ACTIONS_ANGANG) {
                res.factor += 2;
            } else if (gang.type == Action.ACTIONS_MINGGANG) {
                res.factor += 1;
            } else if (gang.type == Action.ACTIONS_JIAGANG) {
                res.factor += 1;
            }
        }
        res.base = res.point * res.factor;
        let notifies = [];
        let ret = res;
        let gangs = yield db.collection(g.colls.gangs).find({
            roomNumber: room.number
        }).toArray();
        ret.gangs = gangs;
        ret.zimoCard = zimoCard;
        ret.winUser = user.unionid;
        let allCards = yield db.collection(g.colls.cards).find({
            roomNumber: room.number
        }).toArray();
        ret.allCards = allCards;
        ret.type = Game.Result.TYPE_ZIMO;
        //结算分数
        //赢的玩家加分
        let winPoint = res.base * 3;
        yield db.collection(g.colls.users).updateOne({
            unionid: user.unionid
        }, {
            $inc: {
                point: winPoint,
                gamePoint: winPoint,
                zimoCount: 1 //自摸次数+1
            }
        });
        //每个输的玩家减分
        let others = users.filter((_user) => {
            return user.unionid != _user.unionid;
        });
        for (let other of others) {
            let losePoint = res.base;
            yield db.collection(g.colls.users).updateOne({
                unionid: other.unionid
            }, {
                $inc: {
                    point: -losePoint,
                    gamePoint: -losePoint,
                }
            });
        }
        for (let _user of users) {
            notifies.push({
                method: "onZimo",
                to: _user.unionid,
                data: ret
            });
        }
        //保存结算信息到数据库
        yield db.collection(g.colls.games).updateOne({
            _id: game._id
        }, {
            $set: {
                result: ret
            }
        });
        return notifies;
    });
    return {
        create: co.wrap(function*(devStatus) {
            yield micro.startServer();
            //生成一局新游戏
            let newGame = co.wrap(function*(room, allUsers, tmpDirections, allCards, host) {
                yield seneca.actAsync({
                    role: "lock",
                    cmd: "acquire",
                    key: "newGame"
                });
                try {
                    let notifies = [];
                    //生成一局所有的牌
                    if (!allCards) {
                        allCards = majiang.shuffledAllCards();
                    }
                    //生成一局game
                    let game = {
                        roomNumber: room.number
                    };
                    yield db.collection(g.colls.games).insertOne(game);
                    //把牌插入数据库
                    for (let tmpCard of allCards) {
                        tmpCard.roomNumber = room.number;
                    }
                    yield db.collection(g.colls.cards).insertMany(allCards);
                    //如果有打乱的方向，就把用户方向打乱
                    if (tmpDirections) {
                        for (let i = 0; i < allUsers.length; i++) {
                            let tmpUser = allUsers[i];
                            tmpUser.roomDirection = tmpDirections[i];
                            yield db.collection(g.colls.users).updateOne({
                                unionid: tmpUser.unionid
                            }, {
                                $set: {
                                    roomDirection: tmpDirections[i]
                                }
                            });
                        }
                    }
                    //如果不指定庄家，就随机生成庄家
                    let hostDirection;
                    let rollPoint1;
                    let rollPoint2;
                    //生成骰子点数
                    rollPoint1 = utils.randomRange(1, 6);
                    rollPoint2 = utils.randomRange(1, 6);
                    let rollPoint = rollPoint1 + rollPoint2;
                    if (!host) {
                        //决定庄家以及当庄的方位
                        hostDirection = yield(co.wrap(function() {
                            if (devStatus == g.DEV_STATUS_PRO || devStatus == g.DEV_STATUS_TEST) {
                                if ([1, 5, 9].contains(rollPoint)) {
                                    return Room.DIRECTION_EAST;
                                } else if ([2, 6, 10].contains(rollPoint)) {
                                    return Room.DIRECTION_NORTH;
                                } else if ([3, 7, 11].contains(rollPoint)) {
                                    return Room.DIRECTION_WEST;
                                } else if ([4, 8, 12].contains(rollPoint)) {
                                    return Room.DIRECTION_SOUTH;
                                }
                            }
                            return Room.DIRECTION_EAST;
                        }))();
                        host = (() => {
                            for (let tmpUser of allUsers) {
                                if (tmpUser.roomDirection == hostDirection) {
                                    return tmpUser.unionid;
                                }
                            }
                        })();
                    } else {
                        hostDirection = yield(co.wrap(function() {
                            for (let user of allUsers) {
                                if (user.unionid == host) {
                                    return user.roomDirection;
                                }
                            }
                        }))();
                    }
                    //把当前房间的方向设置成庄家的方向，把房间当前的gameId设置好
                    room.currentGameId = game._id;
                    room.direction = hostDirection;
                    room.host = host;
                    room.rollPoint1 = rollPoint1;
                    room.rollPoint2 = rollPoint2;
                    yield db.collection(g.colls.rooms).updateOne({
                        number: room.number,
                    }, {
                        $set: {
                            direction: hostDirection,
                            currentGameId: game._id,
                            currentGameIndex: room.currentGameIndex + 1,
                            rollPoint1,
                            rollPoint2,
                            host
                        },
                        $unset: {
                            lastCard: 0
                        }
                    });
                    //给所有玩家发送开始信息
                    for (let i = 0; i < allUsers.length; i++) {
                        let tmpUser = allUsers[i];
                        let data = {
                            users: allUsers,
                            rollPoint1,
                            rollPoint2, //骰子的点
                            host, //庄家
                        };
                        let cards;
                        if (host == tmpUser.unionid) {
                            let count;
                            if (room.mode == 'alpha') {
                                count = 9;
                            } else {
                                count = 14;
                            }
                            cards = allCards.splice(0, count);
                            let _cards = yield db.collection(g.colls.cards).find({
                                roomNumber: room.number,
                                userId: {
                                    $exists: false
                                }
                            }).limit(count).toArray();
                            let cardIds = _cards.map((card) => {
                                return card._id;
                            });
                            yield db.collection(g.colls.cards).updateMany({
                                _id: {
                                    $in: cardIds
                                }
                            }, {
                                $set: {
                                    userId: tmpUser.unionid,
                                    status: Card.STATUS_ONHAND
                                }
                            });
                        } else {
                            let count;
                            if (room.mode == 'alpha') {
                                count = 8;
                            } else {
                                count = 13;
                            }
                            cards = allCards.splice(0, count);
                            let _cards = yield db.collection(g.colls.cards).find({
                                roomNumber: room.number,
                                userId: {
                                    $exists: false
                                }
                            }).limit(count).toArray();
                            let cardIds = _cards.map((card) => {
                                return card._id;
                            });
                            yield db.collection(g.colls.cards).updateMany({
                                _id: {
                                    $in: cardIds
                                }
                            }, {
                                $set: {
                                    userId: tmpUser.unionid,
                                    status: Card.STATUS_ONHAND
                                }
                            });
                        }
                        for (let _card of cards) {
                            _card.status = Card.STATUS_ONHAND;
                        }
                        data.cards = cards;
                        data.actions = [];
                        let res = majiang[room.mode].tingCards(data.cards);
                        if (res.r) {
                            data.actions.push({
                                type: Action.ACTIONS_TING,
                                tingCards: res.tingCards || [],
                                userId: tmpUser.unionid,
                                roomNumber: tmpUser.roomNumber,
                                status: Action.STATUS_WAIT,
                            });
                        }
                        if (tmpUser.unionid == host) {
                            //如果是庄家，判断可以做的动作
                            let action = {
                                userId: tmpUser.unionid,
                                type: Action.ACTIONS_DA,
                                roomNumber: tmpUser.roomNumber,
                                gameId: game._id,
                                status: Action.STATUS_WAIT,
                            };
                            data.actions.push(action);
                            let res = majiang[room.mode].canZiMo(data.cards);
                            if (res) {
                                let action = {
                                    userId: tmpUser.unionid,
                                    type: Action.ACTIONS_ZIMO,
                                    roomNumber: tmpUser.roomNumber,
                                    gameId: game._id,
                                    status: Action.STATUS_WAIT,
                                    isTianHu: true, //这个时候自摸的话，是天胡
                                };
                                data.actions.push(action);
                            }
                            res = majiang[room.mode].canAnGang(data.cards);
                            for (let action2 of res.actions) {
                                action2.userId = tmpUser.unionid;
                                action2.roomNumber = tmpUser.roomNumber;
                                action2.gameId = game._id;
                                action2.status = Action.STATUS_WAIT;
                            }
                            data.actions.push(...res.actions);
                        }
                        if (data.actions.length) {
                            yield db.collection(g.colls.actions).insertMany(data.actions);
                        }
                        //给每个用户发送onReady的通知
                        data.room = room;
                        notifies.push({
                            to: tmpUser.unionid,
                            data: data,
                            method: "onReady"
                        });
                    }
                    return notifies;
                } finally {
                    yield seneca.actAsync({
                        role: "lock",
                        cmd: "release",
                        key: "newGame"
                    });
                }
            });
            nextId = co.wrap(function*(collectionName) {
                let sequeces = db.collection("sequeces");
                var sequece = yield sequeces.findOne({
                    name: collectionName
                });
                if (sequece) {
                    var result = yield sequeces.findAndModify({
                        name: collectionName
                    }, [], {
                        $inc: {
                            val: utils.randomRange(1, 10)
                        }
                    }, {
                        new: true
                    });
                    return Promise.resolve(result.value.val);
                } else {
                    let n = utils.randomRange(10000, 15000);
                    yield sequeces.insertOne({
                        name: collectionName,
                        val: n
                    });
                    return Promise.resolve(n);
                }
            });
            redisClients = yield(co.wrap(function() {
                const password = "sdasdascah1poasdjcb_yuaowe173";
                let port, host;
                if (devStatus == g.DEV_STATUS_UNITTEST) {
                    host = "redis";
                    port = 6379;
                } else if (devStatus == g.DEV_STATUS_TEST) {
                    host = "10.116.75.14";
                    port = 6393;
                } else if (devStatus == g.DEV_STATUS_PRO) {
                    host = "10.116.75.14";
                    port = 6394;
                }
                return {
                    sessionClient: new Redis({
                        port: port,
                        host: host,
                        family: 4,
                        password: password,
                        db: 1
                    }),
                    nonceClient: new Redis({
                        port: port,
                        host: host,
                        family: 4,
                        password: password,
                        db: 2
                    }),
                    attackClient: new Redis({
                        port: port,
                        host: host,
                        family: 4,
                        password: password,
                        db: 3
                    }),
                };
            }))();
            sessionExpire = (() => {
                if (devStatus == g.DEV_STATUS_UNITTEST) {
                    return 600;
                } else if (devStatus == g.DEV_STATUS_TEST) {
                    return 6000;
                } else if (devStatus == g.DEV_STATUS_PRO) {
                    return 7 * 24 * 60 * 60;
                }
            })();
            checkSession = co.wrap(function*(session) {
                let unionid = yield redisClients.sessionClient.get(session);
                //判断是否登录
                if (!unionid) {
                    return Promise.reject({
                        errcode: 101,
                        info: "session不存在"
                    });
                }
                //判断不存在的登录用户
                let user = yield db.collection(g.colls.users).findOne({
                    unionid: unionid
                });
                if (!user) {
                    redisClients.sessionClient.del(session);
                    return Promise.reject({
                        errcode: 1,
                        info: "不存在的登录用户"
                    });
                }
                //session续期
                redisClients.sessionClient.multi().expire(session, sessionExpire).exec();
                return Promise.resolve(user);
            });
            //room用户所在房间，cards用户的牌，是可选的参数，避免从数据库重复查询
            getUserGameInfo = co.wrap(function*(user, room, cards) {
                let gameInfo = {};
                //获取用户手中的所有牌
                if (!room) {
                    room = yield db.collection(g.colls.rooms).findOne({
                        number: user.roomNumber
                    });
                    if (!room) {
                        return Promise.resolve(gameInfo);
                    }
                }
                let remainCardCount;
                if (!cards) {
                    cards = yield db.collection(g.colls.cards).find({
                        userId: user.unionid,
                        status: {
                            $ne: Card.STATUS_INIT
                        },
                    }).toArray();
                }
                remainCardCount = yield db.collection(g.colls.cards).find({
                    roomNumber: room.number,
                    status: {
                        $eq: Card.STATUS_INIT
                    },
                }).count();
                let users = yield db.collection(g.colls.users).find({
                    roomNumber: user.roomNumber
                }).toArray();
                let others = users.filter((_user) => {
                    return _user.unionid != user.unionid;
                });
                let game = yield db.collection(g.colls.games).findOne({
                    _id: room.currentGameId
                });
                gameInfo.users = others;
                gameInfo.room = room;
                gameInfo.otherInfos = [];
                gameInfo.game = game;
                for (let other of others) {
                    let _cards = yield db.collection(g.colls.cards).find({
                        userId: other.unionid,
                        status: {
                            $in: [Card.STATUS_BEIDA, Card.STATUS_PENG, Card.STATUS_ANGANG, Card.STATUS_MINGGANG, Card.STATUS_JIAGANG]
                        },
                    }).toArray();
                    let handCards = yield db.collection(g.colls.cards).find({
                        userId: other.unionid,
                        status: Card.STATUS_ONHAND,
                    }).toArray();
                    gameInfo.otherInfos.push({
                        user: other,
                        cards: _cards,
                        handCardsCount: handCards.length
                    });
                }
                //用户手中有牌，即在一局游戏中
                if (cards.length > 0) {
                    gameInfo.cards = cards;
                    gameInfo.actions = yield db.collection(g.colls.actions).find({
                        roomNumber: user.roomNumber
                    }).toArray();
                    gameInfo.remainCardCount = remainCardCount;
                }
                //还需返回这局游戏其他用户的桌面上的牌和被打的牌
                return Promise.resolve(gameInfo);
            });
            return {
                db,
                log: co.wrap(function*(msg) {
                    let obj;
                    let type = typeof(msg);
                    if (type == "string") {
                        obj = {};
                        obj.msg = msg;
                    } else if (type == "object") {
                        obj = msg;
                    } else {
                        return Promise.reject("错误的参数");
                    }
                    obj.time = new Date().getTime();
                    yield db.collection(g.colls.logs).insert(obj);
                }),
                redisClients: redisClients,
                initDb: co.wrap(function*() {
                    const port = "27017",
                        username = "liuwei4root2",
                        password = "dongtingmajiang18874143580";
                    let ip, dbName;
                    switch (devStatus) {
                        case g.DEV_STATUS_UNITTEST:
                            ip = "mongodb";
                            dbName = "majiang_unit_test";
                            break;
                        case g.DEV_STATUS_TEST:
                            ip = "10.116.75.14";
                            dbName = "renren_majiang_test";
                            break;
                        case g.DEV_STATUS_PRO:
                            ip = "10.116.75.14";
                            dbName = "renren_majiang";
                            break;

                    }
                    let url = "mongodb://" + username + ":" + password + "@" + ip + ":" + port + "/" + dbName;
                    if (devStatus == g.DEV_STATUS_UNITTEST) {
                        url = "mongodb://" + ip + ":" + port + "/" + dbName;
                    }
                    db = yield MongoClient.connect(url, {
                        poolSize: 20,
                        keepAlive: 3 * 60 * 60 * 1000,
                        reconnectTries: 100,
                        reconnectInterval: 5000,
                    });
                    return db;
                }),
                wxLogin: co.wrap(function*(code, ip) {
                    let wxUserInfoRes;
                    if (devStatus == g.DEV_STATUS_UNITTEST) {
                        //单元测试模拟的时候，把code当成unionid
                        wxUserInfoRes = {
                            unionid: code
                        };
                    } else {
                        //先根据code查询openid
                        let wxAccessTokenRes = yield rp({
                            uri: WX_ACCESSTOKEN_URL,
                            qs: {
                                appid: WX_APPID,
                                secret: WX_APPKEY,
                                code: code,
                                grant_type: "authorization_code"
                            },
                            headers: {},
                            json: true
                        });
                        if (wxAccessTokenRes.errcode) {
                            return Promise.reject({
                                method: "wxLogin",
                                errcode: 200,
                                info: wxAccessTokenRes
                            });
                        }
                        //根据openid和access_token查询微信中的用户信息
                        wxUserInfoRes = yield rp({
                            uri: WX_USERINFO_URL,
                            qs: {
                                access_token: wxAccessTokenRes.access_token,
                                openid: wxAccessTokenRes.openid
                            },
                            headers: {},
                            json: true
                        });
                    }
                    if (wxUserInfoRes.errcode) {
                        return Promise.reject({
                            method: "wxLogin",
                            errcode: 201,
                            info: wxUserInfoRes
                        });
                    }
                    //如果表中没有unionid的用户，绑定unionid到用户，否则更新用户
                    let session = utils.randomString(32);
                    let user = yield db.collection(g.colls.users).findOne({
                        unionid: wxUserInfoRes.unionid
                    });
                    if (!user) {
                        user = new User();
                        user.id = yield nextId(g.colls.users);
                        user.unionid = wxUserInfoRes.unionid;
                        user.nickname = wxUserInfoRes.nickname;
                        user.sex = wxUserInfoRes.sex;
                        user.headimgurl = wxUserInfoRes.headimgurl;
                        user.roomCard = 10;
                        user.ip = ip;
                        yield db.collection(g.colls.users).insertOne(user);
                    } else {
                        yield db.collection(g.colls.users).updateOne({
                            unionid: user.unionid
                        }, {
                            $set: {
                                nickname: wxUserInfoRes.nickname,
                                sex: wxUserInfoRes.sex,
                                headimgurl: wxUserInfoRes.headimgurl,
                                ip: ip
                            }
                        });
                    }
                    yield redisClients.sessionClient.multi().set(session, wxUserInfoRes.unionid).expire(session, sessionExpire).exec();
                    let notices = yield db.collection(g.colls.notices).find().sort({
                        prior: -1,
                        _id: -1
                    }).toArray();
                    let notice = notices[0] && notices[0].content || "";
                    notice = notice.replace('\n', '');
                    let tishi = yield db.collection(g.colls.tishis).findOne({});
                    tishi = (tishi && tishi.content || "").replace('\n', '');
                    //返回微信用户信息和游戏信息
                    return Promise.resolve({
                        method: "wxLogin",
                        data: {
                            user: user,
                            session: session,
                            gameInfo: yield getUserGameInfo(user),
                            msg: notice,
                            buyMsg: tishi
                        }
                    });
                }),
                createRoom: co.wrap(function*(session, opts) {
                    yield seneca.actAsync({
                        role: "lock",
                        cmd: "acquire",
                        key: "room"
                    });
                    try {
                        opts = opts || {};
                        opts.birdCount = 0; //默认是没有鸟的
                        if (opts.mode != 1 && opts.mode != 2) {
                            return Promise.reject({
                                method: "createRoom",
                                errcode: 202,
                                info: "没有指定房间的模式"
                            });
                        }
                        if (devStatus == g.DEV_STATUS_UNITTEST) {
                            opts.maxGame = opts.maxGame || 2;
                        }
                        if (devStatus != g.DEV_STATUS_UNITTEST) {
                            if (![Room.DEFAUL_MAX_GAME, Room.DEFAUL_MAX_GAME * 2].contains(opts.maxGame)) {
                                return Promise.reject({
                                    method: "createRoom",
                                    errcode: 1,
                                    info: "游戏局数参数错误"
                                });
                            }
                        }
                        let user = yield checkSession(session);
                        if (user.roomNumber) {
                            return Promise.reject({
                                method: "createRoom",
                                errcode: 200,
                                info: "已经在房间"
                            });
                        }
                        if (!user.roomCard || user.roomCard < ((opts.maxGame / Room.DEFAUL_MAX_GAME) | 0)) {
                            return Promise.reject({
                                method: "createRoom",
                                errcode: 201,
                                info: "房卡数量不足"
                            });
                        }
                        let room;
                        let number;
                        do {
                            number = utils.randomRange(100000, 999999);
                            //检测号码是否已经存在
                            room = yield db.collection(g.colls.rooms).findOne({
                                number: number
                            });
                        } while (room);
                        //插入房间
                        room = new Room();
                        room.number = number;
                        room.owerId = user.unionid;
                        room.birdCount = opts.birdCount;
                        room.currentGameIndex = 0;
                        room.maxGame = opts.maxGame;
                        if (opts.mode == 1) {
                            room.mode = "alpha";
                        } else {
                            room.mode = "beta";
                        }
                        yield db.collection(g.colls.rooms).insertOne(room);
                        //设置用户房间号，积分，方向
                        yield db.collection(g.colls.users).updateOne({
                            unionid: user.unionid
                        }, {
                            $set: {
                                point: USER_DEFULT_POINT,
                                roomDirection: Room.DIRECTION_EAST,
                                roomNumber: number
                            }
                        });
                        return Promise.resolve({
                            data: room,
                            method: "createRoom"
                        });
                    } finally {
                        yield seneca.actAsync({
                            role: "lock",
                            cmd: "release",
                            key: "room"
                        });
                    }
                }),
                joinRoom: co.wrap(function*(session, roomNumber, /**这个参数传进来方便调试用**/ allCards) {
                    yield seneca.actAsync({
                        role: "lock",
                        cmd: "acquire",
                        key: "room"
                    });
                    try {
                        if (roomNumber == null) {
                            return Promise.reject({
                                method: "joinRoom",
                                errcode: 200,
                                info: "没有指定加入的房间"
                            });
                        }
                        let user = yield checkSession(session);
                        //判断是否已经在房间
                        if (user.roomNumber) {
                            return Promise.reject({
                                method: "joinRoom",
                                errcode: 201,
                                info: "已经在房间"
                            });
                        }
                        //判断房间是否存在
                        let room = yield db.collection(g.colls.rooms).findOne({
                            number: roomNumber
                        });
                        if (!room) {
                            return Promise.reject({
                                method: "joinRoom",
                                errcode: 202,
                                info: "房间不存在"
                            });
                        }
                        //查询房间内之前加入的所有用户
                        let users = yield db.collection(g.colls.users).find({
                            roomNumber: roomNumber
                        }).toArray();
                        if (users.length > 3) {
                            return Promise.reject({
                                method: "joinRoom",
                                errcode: 203,
                                info: "房间人数已满"
                            });
                        }
                        if (users.length < 1) {
                            return Promise.reject({
                                errcode: 1,
                                info: "加入房间时，房间人数至少要有1个人了"
                            });
                        }
                        let directions = users.map((tmpUser) => {
                            return tmpUser.roomDirection;
                        });
                        let roomDirection = (() => {
                            //四个方向中不包括哪个方向，就返回哪个方向为当前用户的方向
                            for (let i of Room.DIRECTIONS) {
                                if (directions.indexOf(i) < 0) {
                                    return i;
                                }
                            }
                        })();
                        //设置用户房间号，积分，方向
                        var obj = {
                            roomNumber: roomNumber,
                            point: USER_DEFULT_POINT,
                            roomDirection: roomDirection
                        };
                        yield db.collection(g.colls.users).updateOne({
                            unionid: user.unionid
                        }, {
                            $set: obj
                        });
                        user.roomDirection = roomDirection;
                        user.point = USER_DEFULT_POINT;
                        user.roomNumber = roomNumber;
                        // 如果用户已经有了3个，发送打骰通知
                        if (users.length == 3) {
                            let allUsers = users.concat(user);
                            //打乱用户方位顺序
                            let tmpDirections = Room.DIRECTIONS.clone();
                            if (devStatus == g.DEV_STATUS_PRO || devStatus == g.DEV_STATUS_TEST) {
                                tmpDirections = tmpDirections.shuffle();
                            }
                            //房主房卡数量减少
                            let descCount = (() => {
                                if (devStatus == g.DEV_STATUS_UNITTEST) {
                                    return 1;
                                } else {
                                    return room.maxGame / Room.DEFAUL_MAX_GAME;
                                }
                            })();
                            yield db.collection(g.colls.users).updateOne({
                                unionid: room.owerId
                            }, {
                                $inc: {
                                    roomCard: -descCount
                                }
                            });
                            //生成一局game
                            let notifies = yield newGame(room, allUsers, tmpDirections, allCards);
                            return Promise.resolve({
                                notifies: notifies
                            });
                        } else {
                            let notifies = [];
                            //发送新进入的通知
                            for (let tmpUser of users) {
                                notifies.push({
                                    to: tmpUser.unionid,
                                    method: "onNewJoinRoom",
                                    data: {
                                        user: user,
                                        room: room
                                    }
                                });
                            }
                            return Promise.resolve({
                                data: {
                                    users: users.concat(user),
                                    room: room
                                },
                                method: "joinRoom",
                                notifies: notifies
                            });
                        }
                    } finally {
                        yield seneca.actAsync({
                            role: "lock",
                            cmd: "release",
                            key: "room"
                        });
                    }

                }),
                dismissRoom: co.wrap(function*(session) {
                    yield seneca.actAsync({
                        role: "lock",
                        cmd: "acquire",
                        key: "room"
                    });
                    try {
                        let user = yield checkSession(session);
                        //检测是否有解散房间的权限
                        let room = yield db.collection(g.colls.rooms).findOne({
                            number: user.roomNumber
                        });
                        if (!room) {
                            return Promise.reject({
                                method: "dismissRoom",
                                errcode: 201,
                                info: "这个用户没有房间"
                            });
                        }
                        let users = yield db.collection(g.colls.users).find({
                            roomNumber: user.roomNumber
                        }).toArray();
                        if (users.length == 4) {
                            return Promise.reject({
                                method: "dismissRoom",
                                errcode: 202,
                                info: "已经开始，需要申请才能解散房间"
                            });
                        }
                        let others = users.filter((_user) => {
                            return _user.unionid != user.unionid;
                        });
                        yield clearRoom(user.roomNumber);
                        //需要通知房间其他用户房间被解散
                        let notifies = [];
                        for (let _user of others) {
                            notifies.push({
                                to: _user.unionid,
                                method: "onDismissRoom",
                                data: {
                                    roomNumber: user.roomNumber
                                }
                            });
                        }
                        return Promise.resolve({
                            method: "dismissRoom",
                            data: "ok",
                            notifies: notifies
                        });
                    } finally {
                        yield seneca.actAsync({
                            role: "lock",
                            cmd: "release",
                            key: "room"
                        });
                    }
                }),
                voteDismissRoom: co.wrap(function*(session, isAgreeDismiss) {
                    yield seneca.actAsync({
                        role: "lock",
                        cmd: "acquire",
                        key: "room"
                    });
                    try {
                        let me = yield checkSession(session);
                        if (!me.roomNumber) {
                            return Promise.reject({
                                method: "voteDismissRoom",
                                errcode: 200,
                                info: "这个用户没有房间"
                            });
                        }
                        if (me.isAgreeDismiss != null) {
                            return Promise.reject({
                                method: "voteDismissRoom",
                                errcode: 202,
                                info: "用户已经投过票了"
                            });
                        }
                        let room = yield db.collection(g.colls.rooms).findOne({
                            number: me.roomNumber
                        });
                        let allUsers = yield db.collection(g.colls.users).find({
                            roomNumber: me.roomNumber
                        }).toArray();
                        if (allUsers.length != 4) {
                            return Promise.reject({
                                method: "voteDismissRoom",
                                errcode: 203,
                                info: "人数不足4人，不能申请解散房间"
                            });
                        }
                        let others = allUsers.filter((_user) => {
                            return _user.unionid != me.unionid;
                        });
                        let notHandleUsers = allUsers.filter((_user) => {
                            return _user.isAgreeDismiss == null;
                        });
                        let notifies = [];
                        //当第一个人提起时，算投票同意，其他所有人要发送一个通知
                        if (notHandleUsers.length == 4) {
                            yield db.collection(g.colls.users).updateOne({
                                unionid: me.unionid
                            }, {
                                $set: {
                                    isAgreeDismiss: true,
                                }
                            });
                            let users = yield db.collection(g.colls.users).find({
                                roomNumber: me.roomNumber
                            }).toArray();
                            users = users.map((user) => {
                                return {
                                    unionid: user.unionid,
                                    isAgreeDismiss: user.isAgreeDismiss
                                };
                            });
                            for (let _user of allUsers) {
                                notifies.push({
                                    to: _user.unionid,
                                    method: "onVoteDismissRoom",
                                    data: {
                                        users
                                    }
                                });
                            }
                            //300秒后如果没有2个或以上的人投反对票，则自动解散，每人发送一个取消解散房间的通知
                            let timeout = 300 * 1000;
                            if (devStatus == g.DEV_STATUS_UNITTEST) {
                                timeout = 1 * 1000;
                            } else if (devStatus == g.DEV_STATUS_TEST) {
                                timeout = 60 * 1000;
                            }
                            let timer = setTimeout(co.wrap(function*() {
                                yield clearRoom(me.roomNumber);
                                let notifies = [];
                                let users = allUsers.map((user2) => {
                                    return {
                                        unionid: user2.unionid,
                                        point: user2.point,
                                        fangpaoCount: user2.fangpaoCount || 0,
                                        jiePaoCount: user2.jiePaoCount || 0,
                                        zimoCount: user2.zimoCount || 0,
                                    };
                                });
                                yield db.collection(g.colls.users).updateMany({
                                    roomNumber: me.roomNumber
                                }, {
                                    $unset: {
                                        isAgreeDismiss: 0
                                    }
                                });
                                for (let _user of allUsers) {
                                    notifies.push({
                                        to: _user.unionid,
                                        method: "onDismissRoom",
                                        data: {
                                            roomNumber: me.roomNumber,
                                            users
                                        }
                                    });
                                }
                                yield sleep(500);
                                g.event.emit("notifies", notifies);
                            }), timeout);
                            roomClearTimerMap.set(me.roomNumber, timer);
                        } else {
                            isAgreeDismiss = !!isAgreeDismiss;
                            yield db.collection(g.colls.users).updateOne({
                                unionid: me.unionid
                            }, {
                                $set: {
                                    isAgreeDismiss
                                }
                            });
                            let agreeUsers = (() => {
                                let users = allUsers.filter((_user) => {
                                    return _user.isAgreeDismiss;
                                });
                                if (isAgreeDismiss) {
                                    users.push(me);
                                }
                                return users;
                            })();
                            //如果超过3个以上的人同意，房间解散，每人发送一个取消解散房间的通知
                            if (agreeUsers.length >= 3) {
                                let timer = roomClearTimerMap.get(me.roomNumber);
                                clearTimeout(timer);
                                roomClearTimerMap.delete(me.roomNumber);
                                yield clearRoom(me.roomNumber);
                                let users = allUsers.map((user2) => {
                                    return {
                                        unionid: user2.unionid,
                                        point: user2.point,
                                        fangpaoCount: user2.fangpaoCount || 0,
                                        jiePaoCount: user2.jiePaoCount || 0,
                                        zimoCount: user2.zimoCount || 0,
                                    };
                                });
                                yield db.collection(g.colls.users).updateMany({
                                    roomNumber: me.roomNumber
                                }, {
                                    $unset: {
                                        isAgreeDismiss: 0
                                    }
                                });
                                for (let _user of allUsers) {
                                    notifies.push({
                                        to: _user.unionid,
                                        method: "onDismissRoom",
                                        data: {
                                            roomNumber: me.roomNumber,
                                            users
                                        }
                                    });
                                }
                            } else {
                                let disagreeUsers = (() => {
                                    let users = allUsers.filter((_user) => {
                                        return _user.isAgreeDismiss == false;
                                    });
                                    if (!isAgreeDismiss) {
                                        users.push(me);
                                    }
                                    return users;
                                })();
                                //如果超过2个以上的人不同意，每人发送一个取消解散房间的通知
                                if (disagreeUsers.length >= 2) {
                                    let timer = roomClearTimerMap.get(me.roomNumber);
                                    clearTimeout(timer);
                                    roomClearTimerMap.delete(me.roomNumber);
                                    yield db.collection(g.colls.users).updateMany({
                                        roomNumber: me.roomNumber
                                    }, {
                                        $unset: {
                                            isAgreeDismiss: 0
                                        }
                                    });
                                    for (let _user of allUsers) {
                                        notifies.push({
                                            to: _user.unionid,
                                            method: "onCancelDismissRoom",
                                            data: {
                                                roomNumber: me.roomNumber
                                            }
                                        });
                                    }
                                } else {
                                    let users = yield db.collection(g.colls.users).find({
                                        roomNumber: me.roomNumber
                                    }).toArray();
                                    users = users.map((user) => {
                                        return {
                                            unionid: user.unionid,
                                            isAgreeDismiss: user.isAgreeDismiss
                                        };
                                    });
                                    for (let _user of allUsers) {
                                        notifies.push({
                                            to: _user.unionid,
                                            method: "onVoteDismissRoom",
                                            data: {
                                                users
                                            }
                                        });
                                    }
                                }
                            }
                        }
                        return Promise.resolve({
                            notifies
                        });
                    } finally {
                        yield seneca.actAsync({
                            role: "lock",
                            cmd: "release",
                            key: "room"
                        });
                    }
                }),
                quitRoom: co.wrap(function*(session) {
                    yield seneca.actAsync({
                        role: "lock",
                        cmd: "acquire",
                        key: "room"
                    });
                    try {
                        let user = yield checkSession(session);
                        let room = yield db.collection(g.colls.rooms).findOne({
                            number: user.roomNumber
                        });
                        if (!room) {
                            return Promise.reject({
                                method: "quitRoom",
                                errcode: 200,
                                info: "这个用户没有房间"
                            });
                        }
                        //加上下面的话，就是房间必须由房主解散
                        if (room.owerId == user.unionid) {
                            return Promise.reject({
                                method: "quitRoom",
                                errcode: 201,
                                info: "房主不能退出房间"
                            });
                        }
                        //如果在游戏中不能退出房间
                        if (room.currentGameId) {
                            return Promise.reject({
                                method: "quitRoom",
                                errcode: 202,
                                info: "在游戏中不能退出房间"
                            });
                        }
                        let users = yield db.collection(g.colls.users).find({
                            roomNumber: user.roomNumber
                        }).toArray();
                        if (users.length == 1) {
                            //如果没有其他用户了，只能解散
                            return Promise.reject({
                                method: "quitRoom",
                                errcode: 202,
                                info: "没有其他用户，只能解散房间"
                            });
                        }
                        let others = users.filter((_user) => {
                            return _user.unionid != user.unionid;
                        });
                        yield db.collection(g.colls.users).updateOne({
                            unionid: user.unionid
                        }, {
                            $unset: unsetUserRoom
                        });
                        let notifies = [];
                        for (let _user of others) {
                            notifies.push({
                                to: _user.unionid,
                                method: "onQuitRoom",
                                data: {
                                    user: user
                                }
                            });
                        }
                        return Promise.resolve({
                            method: "quitRoom",
                            data: "ok",
                            notifies: notifies
                        });
                    } finally {
                        yield seneca.actAsync({
                            role: "lock",
                            cmd: "release",
                            key: "room"
                        });
                    }

                }),
                reconnect: co.wrap(function*(session, ip) {
                    let user = yield checkSession(session);
                    yield db.collection(g.colls.users).updateOne({
                        unionid: user.unionid
                    }, {
                        $set: {
                            ip: ip
                        }
                    });
                    //如果用户没有加入房间，直接返回用户信息
                    let notices = yield db.collection(g.colls.notices).find().sort({
                        prior: -1,
                        _id: -1
                    }).toArray();
                    let notice = notices[0] && notices[0].content || "";
                    notice = notice.replace('\n', '');
                    let tishi = yield db.collection(g.colls.tishis).findOne({});
                    tishi = (tishi && tishi.content || "").replace('\n', '');
                    user.ip = ip;
                    if (!user.roomNumber) {
                        return Promise.resolve({
                            method: "reconnect",
                            data: {
                                user: user,
                                msg: notice,
                                buyMsg: tishi
                            },
                            notifies: []
                        });
                    }
                    //返回房间信息
                    let users = yield db.collection(g.colls.users).find({
                        roomNumber: user.roomNumber
                    }).toArray();
                    let notifies = [];
                    for (let tmpUser of users) {
                        //除了这个掉线的用户，其他的都通知
                        if (user.unionid != tmpUser.unionid) {
                            notifies.push({
                                to: tmpUser.unionid,
                                data: user,
                                method: "onReconnect"
                            });
                        }
                    }
                    let gameInfo = yield getUserGameInfo(user);
                    return Promise.resolve({
                        method: "reconnect",
                        data: {
                            user: user,
                            session: session,
                            gameInfo: gameInfo,
                            msg: notice,
                            buyMsg: tishi
                        },
                        notifies: notifies
                    });
                }),
                daPai: co.wrap(function*(session, cardId) {
                    let user = yield checkSession(session);
                    if (!user.roomNumber) {
                        return Promise.reject({
                            method: "daPai",
                            errcode: 200,
                            info: "这个用户还没有加入房间"
                        });
                    }
                    yield seneca.actAsync({
                        role: "lock",
                        cmd: "acquire",
                        key: "room" + user.roomNumber,
                    });
                    try {
                        user = yield db.collection(g.colls.users).findOne({
                            unionid: user.unionid
                        });
                        let room = yield db.collection(g.colls.rooms).findOne({
                            number: user.roomNumber
                        });
                        let game = yield db.collection(g.colls.games).findOne({
                            _id: room.currentGameId
                        });
                        if (game.result) {
                            return Promise.reject({
                                method: "daPai",
                                errcode: 203,
                                info: "这一局游戏已经结束"
                            });
                        }
                        if (room.direction != user.roomDirection) {
                            return Promise.reject({
                                method: "daPai",
                                errcode: 201,
                                info: "还没有轮到这个用户出牌"
                            });
                        }
                        //需要等待所有操作都完成了才能打牌
                        let action = yield db.collection(g.colls.actions).findOne({
                            roomNumber: room.number,
                            type: {
                                $in: handleActions
                            }
                        });
                        if (action) {
                            return Promise.reject({
                                method: "daPai",
                                errcode: 202,
                                info: "此时不能出牌，需先等待所有用户完成操作"
                            });
                        }
                        let allUsers = yield db.collection(g.colls.users).find({
                            roomNumber: room.number
                        }).toArray();
                        let others = allUsers.filter((_user) => {
                            return _user.unionid != user.unionid;
                        });
                        //打出这一张牌
                        let r = yield db.collection(g.colls.cards).findOneAndUpdate({
                            _id: ObjectID(cardId),
                            status: Card.STATUS_ONHAND,
                            userId: user.unionid,
                            cantDa: {
                                $exists: false
                            }
                        }, {
                            $set: {
                                status: Card.STATUS_BEIDA
                            }
                        }, {
                            returnOriginal: false
                        });
                        let card = r.value;
                        if (!card) {
                            return Promise.reject({
                                errcode: 204,
                                info: "不可以打出这个牌，用户手上没有这个牌，或者用户已经报听"
                            });
                        }
                        //如果用户已经报听，把他的所有手上的牌都设为不能打
                        if (user.isBaoTing) {
                            yield db.collection(g.colls.cards).updateMany({
                                userId: user.unionid,
                                status: Card.STATUS_ONHAND,
                            }, {
                                $set: {
                                    cantDa: true
                                }
                            });
                        }
                        yield db.collection(g.colls.rooms).updateOne({
                            number: room.number
                        }, {
                            $set: {
                                lastCard: card
                            }
                        });
                        //打完牌之后把之前动作全部清空
                        yield db.collection(g.colls.actions).deleteMany({
                            roomNumber: room.number,
                        });
                        let notifies = [];
                        //给其他用户发送打牌的通知
                        for (let _user of others) {
                            notifies.push({
                                to: _user.unionid,
                                method: "onDaPai",
                                data: {
                                    card: card
                                }
                            });
                        }
                        let hasAction = false;
                        for (let _user of others) {
                            //判断打牌后其他人能做的操作，比如吃，碰，接炮，杠
                            let actions = [];
                            //获取其他用户的牌
                            let _cards = yield db.collection(g.colls.cards).find({
                                userId: _user.unionid
                            }).toArray();
                            let res = majiang[room.mode].canMingGang(_cards, card);
                            if (!_user.isBaoTing) {
                                actions.push(...res.actions);
                            }
                            res = majiang[room.mode].canPeng(_cards, card);
                            //如果没有过手并且没有报听，就可以碰
                            let guoShous = yield db.collection(g.colls.guoShous).find({
                                userId: _user.unionid,
                            }).toArray();
                            let pengGuoShous = guoShous.filter((guoshuo) => {
                                if (!guoshuo.guoActions) {
                                    return false;
                                } else {
                                    return guoshuo.guoActions.contains(Action.ACTIONS_PENG) && guoshuo.cardValue == card.value;
                                }
                            });
                            if (!pengGuoShous.length) {
                                actions.push(...res.actions);
                                for (let action of actions) {
                                    action.userId = _user.unionid;
                                    action.roomNumber = room.number;
                                    action.gameId = room.currentGameId;
                                    action.status = Action.STATUS_WAIT;
                                }
                            }
                            let jiepaoGuoShous = guoShous.filter((guoshuo) => {
                                if (!guoshuo.guoActions) {
                                    return false;
                                } else {
                                    return guoshuo.guoActions.contains(Action.ACTIONS_JIEPAO);
                                }
                            });
                            if (!jiepaoGuoShous.length) {
                                let res = majiang[room.mode].canJiePao(_cards, card, _user.isBaoTing, false, room.mamahuEnable, room.yitiaolongEnable);
                                if (res) {
                                    actions.push({
                                        type: Action.ACTIONS_JIEPAO,
                                        myCards: _cards,
                                        otherCard: card,
                                        userId: _user.unionid,
                                        roomNumber: room.number,
                                        gameId: room.currentGameId,
                                        status: Action.STATUS_WAIT,
                                        isBaoTing: !!_user.isBaoTing,
                                    });
                                }
                            }
                            if (actions.length) {
                                //如果打牌后其他用户有操作，默认要加入“过”的操作
                                actions.push({
                                    type: Action.ACTIONS_GUO,
                                    userId: _user.unionid,
                                    roomNumber: room.number,
                                    gameId: room.currentGameId,
                                    status: Action.STATUS_WAIT,
                                    otherCard: card,
                                    guoActions: actions.map((a) => {
                                        return a.type;
                                    }),
                                });
                                hasAction = true;
                                yield db.collection(g.colls.actions).insertMany(actions);
                                notifies.push({
                                    to: _user.unionid,
                                    data: {
                                        actions: actions
                                    },
                                    method: "onActions"
                                });
                            }
                        }
                        //如果没有可以操作的用户，该下一个用户摸牌了
                        if (!hasAction) {
                            let res = yield nextUserMoPai(room, game, allUsers);
                            notifies.push(...res.notifies);
                        }
                        return Promise.resolve({
                            method: "daPai",
                            data: {
                                card //打掉的那张牌
                            },
                            notifies: notifies
                        });
                    } finally {
                        yield seneca.actAsync({
                            role: "lock",
                            cmd: "release",
                            key: "room" + user.roomNumber,
                        });
                    }
                }),
                action: co.wrap(function*(session, actionId) {
                    let action = yield db.collection(g.colls.actions).findOne({
                        _id: ObjectID(actionId)
                    });
                    if (!action) {
                        return Promise.reject({
                            method: "action",
                            errcode: 200,
                            info: "过期的操作"
                        });
                    }
                    if (!handleActions.contains(action.type)) {
                        return Promise.reject({
                            method: "action",
                            errcode: 204,
                            info: "此接口不支持的操作"
                        });
                    }
                    yield seneca.actAsync({
                        role: "lock",
                        cmd: "acquire",
                        key: "room" + action.roomNumber,
                    });
                    try {
                        action = yield db.collection(g.colls.actions).findOne({
                            _id: ObjectID(actionId)
                        });
                        let notifies = [];
                        let user = yield checkSession(session);
                        if (action.userId != user.unionid) {
                            return Promise.reject({
                                method: "action",
                                errcode: 202,
                                info: "你没有执行这个操作的权限"
                            });
                        }
                        if (action.status == Action.STATUS_CONFIRM) {
                            return Promise.reject({
                                method: "action",
                                errcode: 203,
                                info: "这个操作你已经确定过了"
                            });
                        }
                        let room = yield db.collection(g.colls.rooms).findOne({
                            number: user.roomNumber
                        });
                        let game = yield db.collection(g.colls.games).findOne({
                            _id: room.currentGameId
                        });
                        if (game.result) {
                            return Promise.reject({
                                method: "action",
                                errcode: 204,
                                info: "这局游戏已经结束"
                            });
                        }
                        //获取房间所有没有选定的操作
                        let waitActions = yield db.collection(g.colls.actions).find({
                            roomNumber: user.roomNumber,
                            status: Action.STATUS_WAIT,
                        }).toArray();
                        //其他用户的没有选定的操作
                        let otherWaitActions = waitActions.filter((action) => {
                            return action.userId != user.unionid;
                        });
                        //如果其他用户还有待执行的操作
                        if (otherWaitActions.length) {
                            //把这次操作标记为已经确认的操作
                            yield db.collection(g.colls.actions).updateOne({
                                _id: action._id
                            }, {
                                $set: {
                                    status: Action.STATUS_CONFIRM
                                }
                            });
                            //把此用户其他的候选操作删除
                            yield db.collection(g.colls.actions).deleteMany({
                                userId: user.unionid,
                                status: Action.STATUS_WAIT,
                            });
                        } else {
                            let users = yield db.collection(g.colls.users).find({
                                roomNumber: action.roomNumber
                            }).toArray();
                            //如果其他用户的操作都已经选定，列出其他用户选定的操作
                            let confirmActions = yield db.collection(g.colls.actions).find({
                                roomNumber: room.number,
                                status: Action.STATUS_CONFIRM,
                            }).toArray();
                            //然后可以删除房间所有除了挂起的操作
                            yield db.collection(g.colls.actions).deleteMany({
                                roomNumber: room.number,
                                status: {
                                    $ne: Action.STATUS_PENDING
                                },
                            });
                            confirmActions.push(action); //加上当前的这个选定操作
                            //将过手记录下来
                            let guoActions = confirmActions.filter((_action) => {
                                return _action.type == Action.ACTIONS_GUO;
                            });
                            let guoShous = [];
                            for (let action of guoActions) {
                                guoShous.push({
                                    userId: action.userId,
                                    roomNumer: room.number,
                                    cardValue: action.otherCard.value,
                                    guoActions: action.guoActions
                                });
                            }
                            if (guoShous.length) {
                                yield db.collection(g.colls.guoShous).insertMany(guoShous);
                            }
                            //找出所有接炮的操作
                            let jiePaoActions = confirmActions.filter((action) => {
                                return action.type == Action.ACTIONS_JIEPAO;
                            });
                            //如果有“接炮”的操作，则每个人接炮的人都成功接炮
                            if (jiePaoActions.length) {
                                let hupais = [];
                                for (let action of jiePaoActions) {
                                    let res = majiang[room.mode].jiepao(action.myCards, action.otherCard, action.isBaoTing, action.isQiangGang);
                                    //谁接了谁的炮，放炮的那张牌，哪些门子，赚了多少分
                                    let gangs = yield db.collection(g.colls.gangs).find({
                                        userId: action.userId
                                    }).toArray();
                                    for (let gang of gangs) {
                                        if (gang.type == Action.ACTIONS_ANGANG) {
                                            res.factor += 2;
                                        } else if (gang.type == Action.ACTIONS_MINGGANG) {
                                            res.factor += 1;
                                        } else if (gang.type == Action.ACTIONS_JIAGANG) {
                                            res.factor += 1;
                                        }
                                    }
                                    let hupai = {
                                        gameId: game._id,
                                        winUser: action.userId,
                                        fangPaoUser: action.otherCard.userId,
                                        jiePaoCard: action.otherCard,
                                        menzis: res.menzis,
                                        base: res.factor * res.point,
                                        factor: res.factor,
                                        point: res.point,
                                    };
                                    hupais.push(hupai);
                                }
                                let cards = yield db.collection(g.colls.cards).find({
                                    roomNumber: room.number
                                }).toArray();
                                let gangs = yield db.collection(g.colls.gangs).find({
                                    roomNumber: room.number
                                }).toArray();
                                yield db.collection(g.colls.games).updateOne({
                                    _id: action.gameId
                                }, {
                                    $set: {
                                        result: {
                                            hupais: hupais,
                                            type: Game.Result.TYPE_JIEPAO,
                                            cards: cards,
                                            gangs
                                        }
                                    }
                                });
                                //接炮的结算
                                for (let hupai of hupais) {
                                    //接炮玩家加分
                                    yield db.collection(g.colls.users).updateOne({
                                        unionid: hupai.winUser
                                    }, {
                                        $inc: {
                                            point: hupai.base,
                                            gamePoint: hupai.base,
                                            jiePaoCount: 1 //接炮次数+1
                                        }
                                    });
                                    //放炮玩家减分，并且放炮数量+1
                                    yield db.collection(g.colls.users).updateOne({
                                        unionid: hupai.fangPaoUser
                                    }, {
                                        $inc: {
                                            point: -hupai.base,
                                            gamePoint: -hupai.base,
                                            fangpaoCount: 1,
                                        }
                                    });
                                }
                                for (let _user of users) {
                                    notifies.push({
                                        to: _user.unionid,
                                        method: "onJiePao",
                                        data: {
                                            hupais,
                                            cards,
                                            gangs,
                                        }
                                    });
                                }
                                yield saveRecord(room);
                                return Promise.resolve({
                                    notifies: notifies
                                });
                            } else {
                                //接下来处理明杠的操作，只可能有一个玩家杠
                                let gangAction = (() => {
                                    for (let action of confirmActions) {
                                        if (action.type == Action.ACTIONS_MINGGANG) return action;
                                    }
                                })();
                                if (gangAction) {
                                    let gangUser = yield db.collection(g.colls.users).findOne({
                                        unionid: gangAction.userId
                                    });
                                    //把牌变成自己的牌,并且变成被明杠的牌
                                    yield db.collection(g.colls.cards).updateOne({
                                        _id: gangAction.otherCard._id
                                    }, {
                                        $set: {
                                            status: Card.STATUS_MINGGANG,
                                            userId: gangAction.userId,
                                        }
                                    });
                                    //把用户手中的相同值的牌变成明杠的牌
                                    yield db.collection(g.colls.cards).updateMany({
                                        userId: gangAction.userId,
                                        value: gangAction.otherCard.value,
                                    }, {
                                        $set: {
                                            status: Card.STATUS_MINGGANG,
                                            userId: gangAction.userId,
                                        }
                                    });
                                    //保存这个明杠的信息
                                    yield db.collection(g.colls.gangs).insertOne({
                                        userId: gangAction.userId,
                                        roomNumber: room.number,
                                        otherId: gangAction.otherCard.userId,
                                        type: Action.ACTIONS_MINGGANG
                                    });
                                    let others = users.filter((_user) => {
                                        return _user.unionid != gangAction.userId;
                                    });
                                    //清除这个用户的过手
                                    yield db.collection(g.colls.guoShous).deleteMany({
                                        userId: gangAction.userId
                                    });
                                    //明杠的通知
                                    for (let _user of others) {
                                        notifies.push({
                                            to: _user.unionid,
                                            method: "onActions",
                                            data: {
                                                actions: [gangAction]
                                            }
                                        });
                                    }
                                    //操作成功的通知
                                    notifies.push({
                                        to: gangAction.userId,
                                        method: "actionOk",
                                        data: {
                                            actions: [gangAction]
                                        }
                                    });
                                    //改变房间的方向
                                    yield db.collection(g.colls.rooms).updateOne({
                                        number: gangAction.roomNumber
                                    }, {
                                        $set: {
                                            direction: gangUser.roomDirection
                                        }
                                    });
                                    //获取桌面上的剩余所有牌
                                    let remainCards = yield db.collection(g.colls.cards).find({
                                        roomNumber: gangAction.roomNumber,
                                        status: Card.STATUS_INIT,
                                    }).toArray();
                                    if (remainCards.length <= 1) {
                                        let res = yield nextUserMoPai(room, game, users, remainCards);
                                        notifies.push(...res.notifies);
                                    } else {
                                        let moPaiUser = yield db.collection(g.colls.users).findOne({
                                            unionid: gangAction.userId
                                        });
                                        let res = yield userMoPai(room, moPaiUser, game, users, remainCards, true);
                                        notifies.push(...res.notifies);
                                    }
                                } else {
                                    //接下来处理碰的操作
                                    let pengAction = (() => {
                                        for (let action of confirmActions) {
                                            if (action.type == Action.ACTIONS_PENG) return action;
                                        }
                                    })();
                                    if (pengAction) {
                                        let pengUser = yield db.collection(g.colls.users).findOne({
                                            unionid: pengAction.userId
                                        });
                                        let others = users.filter((_user) => {
                                            return _user.unionid != pengAction.userId;
                                        });
                                        //把自己的牌放桌上
                                        for (let _card of pengAction.myCards) {
                                            yield db.collection(g.colls.cards).updateOne({
                                                _id: _card._id
                                            }, {
                                                $set: {
                                                    status: Card.STATUS_PENG
                                                }
                                            });
                                        }
                                        //把被碰的牌放桌上，并且变成自己的牌
                                        yield db.collection(g.colls.cards).updateOne({
                                            _id: pengAction.otherCard._id
                                        }, {
                                            $set: {
                                                status: Card.STATUS_PENG,
                                                userId: pengAction.userId,
                                            }
                                        });
                                        let action = {
                                            type: Action.ACTIONS_DA,
                                            userId: pengAction.userId,
                                            roomNumber: pengAction.roomNumber,
                                            gameId: pengAction.gameId,
                                            status: Action.STATUS_WAIT,
                                        };
                                        yield db.collection(g.colls.actions).insertOne(action);
                                        //清除这个用户的过手
                                        yield db.collection(g.colls.guoShous).deleteMany({
                                            userId: pengAction.userId
                                        });
                                        //打牌的通知
                                        for (let _user of users) {
                                            notifies.push({
                                                to: _user.unionid,
                                                method: "onActions",
                                                data: {
                                                    actions: [action]
                                                }
                                            });
                                        }
                                        //碰牌的通知
                                        for (let _user of others) {
                                            notifies.push({
                                                to: _user.unionid,
                                                method: "onActions",
                                                data: {
                                                    actions: [pengAction]
                                                }
                                            });
                                        }
                                        //操作成功的通知
                                        notifies.push({
                                            to: pengAction.userId,
                                            method: "actionOk",
                                            data: {
                                                actions: [pengAction]
                                            }
                                        });
                                        //改变房间的方向
                                        yield db.collection(g.colls.rooms).updateOne({
                                            number: pengAction.roomNumber
                                        }, {
                                            $set: {
                                                direction: pengUser.roomDirection
                                            }
                                        });
                                    } else {
                                        //接下来处理吃的操作
                                        let chiAction = (() => {
                                            for (let action of confirmActions) {
                                                if (action.type == Action.ACTIONS_CHI) return action;
                                            }
                                        })();
                                        if (chiAction) {
                                            let chiUser = yield db.collection(g.colls.users).findOne({
                                                unionid: chiAction.userId
                                            });
                                            let others = users.filter((_user) => {
                                                return _user.unionid != chiAction.userId;
                                            });
                                        } else {
                                            //这里处理都是过的情况
                                            let room = yield db.collection(g.colls.rooms).findOne({
                                                number: user.roomNumber
                                            });
                                            //查看是否还有挂起的加杠操作
                                            let jiaGangAction = yield db.collection(g.colls.actions).findOne({
                                                roomNumber: user.roomNumber,
                                                status: Action.STATUS_PENDING,
                                                type: Action.ACTIONS_JIAGANG,
                                            });
                                            if (jiaGangAction) {
                                                let others = users.filter((_user) => {
                                                    return _user.unionid != jiaGangAction.userId;
                                                });
                                                let res = majiang[room.mode].canJiaGang(jiaGangAction.myCards, jiaGangAction.otherCard);
                                                //这里是处理别人可以抢杠，但是放弃的加杠成功情况
                                                let _user = yield db.collection(g.colls.users).findOne({
                                                    unionid: jiaGangAction.userId
                                                });
                                                let r = yield jiaGang(_user, users, others, room, game, jiaGangAction);
                                                notifies.push(...r.notifies);
                                            } else {
                                                //该下一个人摸牌了
                                                let res = yield nextUserMoPai(room, undefined, users);
                                                notifies.push(...res.notifies);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        return Promise.resolve({
                            notifies: notifies
                        });
                    } finally {
                        yield seneca.actAsync({
                            role: "lock",
                            cmd: "release",
                            key: "room" + action.roomNumber,
                        });
                    }
                }),
                zimo: co.wrap(function*(session, actionId) {
                    let user = yield checkSession(session);
                    if (!user.roomNumber) {
                        return Promise.reject({
                            method: "zimo",
                            errcode: 200,
                            info: "用户没有加入房间"
                        });
                    }
                    yield seneca.actAsync({
                        role: "lock",
                        cmd: "acquire",
                        key: "room" + user.roomNumber,
                    });
                    try {
                        user = yield db.collection(g.colls.users).findOne({
                            unionid: user.unionid
                        });
                        let action = yield db.collection(g.colls.actions).findOne({
                            _id: ObjectID(actionId)
                        });
                        if (!action) {
                            return Promise.reject({
                                method: "zimo",
                                errcode: 201,
                                info: "这个操作不存在"
                            });
                        }
                        let room = yield db.collection(g.colls.rooms).findOne({
                            number: user.roomNumber
                        });
                        let game = yield db.collection(g.colls.games).findOne({
                            _id: room.currentGameId
                        });
                        if (game.result) {
                            return Promise.reject({
                                method: "action",
                                errcode: 202,
                                info: "这局游戏已经结束"
                            });
                        }
                        let notifies = yield userZimo(user, room, game, action.isTianHu, action.isGangBao, action.zimoCard);
                        yield saveRecord(room);
                        return Promise.resolve({
                            notifies
                        });
                    } finally {
                        yield seneca.actAsync({
                            role: "lock",
                            cmd: "release",
                            key: "room" + user.roomNumber,
                        });
                    }
                }),
                ting: co.wrap(function*(session, actionId) {
                    let user = yield checkSession(session);
                    if (!user.roomNumber) {
                        return Promise.reject({
                            method: "zimo",
                            errcode: 200,
                            info: "用户没有加入房间"
                        });
                    }
                    yield seneca.actAsync({
                        role: "lock",
                        cmd: "acquire",
                        key: "room" + user.roomNumber,
                    });
                    try {
                        user = yield db.collection(g.colls.users).findOne({
                            unionid: user.unionid
                        });
                        let action = yield db.collection(g.colls.actions).findOne({
                            _id: ObjectID(actionId)
                        });
                        if (!action) {
                            return Promise.reject({
                                method: "zimo",
                                errcode: 201,
                                info: "这个操作不存在"
                            });
                        }
                        let room = yield db.collection(g.colls.rooms).findOne({
                            number: user.roomNumber
                        });
                        let game = yield db.collection(g.colls.games).findOne({
                            _id: room.currentGameId
                        });
                        if (game.result) {
                            return Promise.reject({
                                method: "action",
                                errcode: 202,
                                info: "这局游戏已经结束"
                            });
                        }
                        //设置这个用户为报听
                        yield db.collection(g.colls.users).updateOne({
                            unionid: user.unionid
                        }, {
                            $set: {
                                isBaoTing: true,
                            }
                        });
                        //如果用户已经报听并且只有8张牌，把他的所有手上的牌都设为不能打
                        let count = yield db.collection(g.colls.cards).find({
                            userId: user.unionid
                        }).count();
                        if (count == 8) {
                            yield db.collection(g.colls.cards).updateMany({
                                userId: user.unionid,
                                status: Card.STATUS_ONHAND,
                            }, {
                                $set: {
                                    cantDa: true
                                }
                            });
                        }
                        //删除这个用户的报听action
                        yield db.collection(g.colls.actions).deleteMany({
                            userId: user.unionid,
                            type: Action.ACTIONS_TING
                        });
                        let allUsers = yield db.collection(g.colls.users).find({
                            roomNumber: room.number
                        }).toArray();
                        let others = allUsers.filter((_user) => {
                            return user.unionid != _user.unionid;
                        });
                        let notifies = [];
                        for (let other of others) {
                            notifies.push({
                                to: other.unionid,
                                method: "onTing",
                                data: {
                                    userId: user.unionid
                                }
                            });
                        }
                        return Promise.resolve({
                            method: "ting",
                            notifies,
                            data: {
                                tingCards: action.tingCards
                            }
                        });
                    } finally {
                        yield seneca.actAsync({
                            role: "lock",
                            cmd: "release",
                            key: "room" + user.roomNumber,
                        });
                    }
                }),
                jiaGang: co.wrap(function*(session, actionId) {
                    let user = yield checkSession(session);
                    if (!user.roomNumber) {
                        return Promise.reject({
                            method: "jiaGang",
                            errcode: 200,
                            info: "用户没有加入房间"
                        });
                    }
                    yield seneca.actAsync({
                        role: "lock",
                        cmd: "acquire",
                        key: "room" + user.roomNumber,
                    });
                    try {
                        user = yield db.collection(g.colls.users).findOne({
                            unionid: user.unionid
                        });
                        let action = yield db.collection(g.colls.actions).findOne({
                            _id: ObjectID(actionId)
                        });
                        if (!action) {
                            return Promise.reject({
                                method: "jiaGang",
                                errcode: 201,
                                info: "操作不存在"
                            });
                        }
                        if (action.type != Action.ACTIONS_JIAGANG) {
                            return Promise.reject({
                                method: "jiaGang",
                                errcode: 202,
                                info: "操作类型不是加杠"
                            });
                        }
                        let room = yield db.collection(g.colls.rooms).findOne({
                            number: user.roomNumber
                        });
                        let game = yield db.collection(g.colls.games).findOne({
                            _id: room.currentGameId
                        });
                        if (game.result) {
                            return Promise.reject({
                                method: "action",
                                errcode: 203,
                                info: "这局游戏已经结束"
                            });
                        }
                        let users = yield db.collection(g.colls.users).find({
                            roomNumber: user.roomNumber
                        }).toArray();
                        let others = users.filter((_user) => {
                            return _user.unionid != user.unionid;
                        });
                        let cards = yield db.collection(g.colls.cards).find({
                            userId: user.unionid
                        }).toArray();
                        let notifies = [];
                        //把这个用户的加杠操作挂起
                        yield db.collection(g.colls.actions).updateOne({
                            _id: action._id
                        }, {
                            $set: {
                                status: Action.STATUS_PENDING
                            }
                        });
                        //把这个用户的其他操作删除
                        yield db.collection(g.colls.actions).deleteMany({
                            userId: user.unionid,
                            _id: {
                                $ne: action._id
                            }
                        });
                        //判断是否有人能接炮，这里的接炮是抢杠
                        let jiePaoActions = yield(co.wrap(function*() {
                            let actions = [];
                            for (let _user of others) {
                                let cards = yield db.collection(g.colls.cards).find({
                                    userId: _user.unionid
                                }).toArray();
                                let res = majiang[room.mode].canJiePao(cards, action.otherCard, _user.isBaoTing, true);
                                if (res) {
                                    actions.push({
                                        userId: _user.unionid,
                                        roomNumber: room.number,
                                        gameId: game._id,
                                        myCards: cards,
                                        otherCard: action.otherCard,
                                        type: Action.ACTIONS_JIEPAO,
                                        status: Action.STATUS_WAIT,
                                        isQiangGang: true,
                                        isBaoTing: !!_user.isBaoTing,
                                    });
                                    actions.push({
                                        userId: _user.unionid,
                                        roomNumber: room.number,
                                        gameId: game._id,
                                        type: Action.ACTIONS_GUO,
                                        status: Action.STATUS_WAIT,
                                        otherCard: action.otherCard,
                                    });
                                    notifies.push({
                                        to: _user.unionid,
                                        method: "onActions",
                                        data: {
                                            actions: actions
                                        },
                                    });
                                }
                            }
                            return actions;
                        }))();
                        if (jiePaoActions.length) {
                            yield db.collection(g.colls.actions).insertMany(jiePaoActions);
                        } else {
                            let r = yield jiaGang(user, users, others, room, game, action);
                            notifies.push(...r.notifies);
                        }
                        return Promise.resolve({
                            notifies: notifies
                        });
                    } finally {
                        yield seneca.actAsync({
                            role: "lock",
                            cmd: "release",
                            key: "room" + user.roomNumber,
                        });
                    }
                }),
                anGang: co.wrap(function*(session, actionId) {
                    let user = yield checkSession(session);
                    if (!user.roomNumber) {
                        return Promise.reject({
                            method: "jiaGang",
                            errcode: 200,
                            info: "用户没有加入房间"
                        });
                    }
                    yield seneca.actAsync({
                        role: "lock",
                        cmd: "acquire",
                        key: "room" + user.roomNumber,
                    });
                    try {
                        user = yield db.collection(g.colls.users).findOne({
                            unionid: user.unionid
                        });
                        let action = yield db.collection(g.colls.actions).findOne({
                            _id: ObjectID(actionId)
                        });
                        if (!action) {
                            return Promise.reject({
                                method: "anGang",
                                errcode: 201,
                                info: "指定的操作不存在"
                            });
                        }
                        if (action.userId != user.unionid) {
                            return Promise.reject({
                                method: "anGang",
                                errcode: 202,
                                info: "你没有这个操作的权限"
                            });
                        }
                        if (action.type != Action.ACTIONS_ANGANG) {
                            return Promise.reject({
                                method: "anGang",
                                errcode: 203,
                                info: "操作的类型不是暗杠"
                            });
                        }
                        let room = yield db.collection(g.colls.rooms).findOne({
                            number: user.roomNumber
                        });
                        let game = yield db.collection(g.colls.games).findOne({
                            _id: room.currentGameId
                        });
                        if (game.result) {
                            return Promise.reject({
                                method: "action",
                                errcode: 204,
                                info: "这局游戏已经结束"
                            });
                        }
                        let users = yield db.collection(g.colls.users).find({
                            roomNumber: user.roomNumber
                        }).toArray();
                        let others = users.filter((_user) => {
                            return _user.unionid != user.unionid;
                        });
                        let cards = yield db.collection(g.colls.cards).find({
                            userId: user.unionid
                        }).toArray();
                        //把这个用户的其他操作删除
                        yield db.collection(g.colls.actions).deleteMany({
                            userId: user.unionid
                        });
                        let notifies = [];
                        //把手中这个值的牌都改成暗杠的牌
                        yield db.collection(g.colls.cards).updateMany({
                            userId: user.unionid,
                            value: action.myCards[0].value
                        }, {
                            $set: {
                                status: Card.STATUS_ANGANG
                            }
                        });
                        //记录这次暗杠
                        yield db.collection(g.colls.gangs).insertOne({
                            userId: user.unionid,
                            roomNumber: user.roomNumber,
                            type: Action.ACTIONS_ANGANG
                        });
                        for (let _user of others) {
                            notifies.push({
                                to: _user.unionid,
                                method: "onAnGang",
                                data: {
                                    actions: [action]
                                }
                            });
                        }
                        let remainCards = yield db.collection(g.colls.cards).find({
                            roomNumber: user.roomNumber,
                            status: Card.STATUS_INIT
                        }).toArray();
                        if (remainCards.length <= 1) {
                            //下一个人摸牌
                            let res = yield nextUserMoPai(room, game, users);
                            notifies.push(...res.notifies);
                        } else {
                            //摸一张
                            let res = yield userMoPai(room, user, game, users, remainCards);
                            notifies.push(...res.notifies);
                        }
                        return Promise.resolve({
                            notifies: notifies
                        });
                    } finally {
                        yield seneca.actAsync({
                            role: "lock",
                            cmd: "release",
                            key: "room" + user.roomNumber,
                        });
                    }
                }),
                //确认小局结果
                smallGameOk: co.wrap(function*(session, /**测试用的接口**/ allCards) {
                    let me = yield checkSession(session);
                    if (!me.roomNumber) {
                        return Promise.reject({
                            method: "smallGameOk",
                            errcode: 200,
                            info: "用户没有加入房间"
                        });
                    }
                    if (me.isReady) {
                        return Promise.reject({
                            method: "smallGameOk",
                            errcode: 201,
                            info: "重复操作，用户已经确认了结果"
                        });
                    }
                    yield seneca.actAsync({
                        role: "lock",
                        cmd: "acquire",
                        key: "room" + me.roomNumber,
                    });
                    try {
                        let allUsers = yield db.collection(g.colls.users).find({
                            roomNumber: me.roomNumber
                        }).toArray();
                        let room = yield db.collection(g.colls.rooms).findOne({
                            number: me.roomNumber
                        });
                        //如果已经是最后一把，清除房间信息，返回整局游戏信息
                        if (room.currentGameIndex == room.maxGame) {
                            let notifies = [];
                            for (let _user of allUsers) {
                                notifies.push({
                                    to: _user.unionid,
                                    method: "onGameOver",
                                    data: {
                                        users: allUsers.map((user2) => {
                                            return {
                                                unionid: user2.unionid,
                                                point: user2.point,
                                                fangpaoCount: user2.fangpaoCount || 0,
                                                jiePaoCount: user2.jiePaoCount || 0,
                                                zimoCount: user2.zimoCount || 0,
                                            };
                                        }),
                                    }
                                });
                            }
                            //清除房间信息
                            yield clearRoom(room.number);
                            return Promise.resolve({
                                notifies: notifies
                            });
                        } else {
                            let okUsers = allUsers.filter((user) => {
                                return user.isReady;
                            });
                            //当所有人都确认后
                            if (okUsers.length == 3) {
                                //重置房间内所有玩家此局的分数
                                yield db.collection(g.colls.users).updateMany({
                                    roomNumber: room.number
                                }, {
                                    $unset: {
                                        gamePoint: 0,
                                        isReady: 0
                                    }
                                });
                                let game = yield db.collection(g.colls.games).findOne({
                                    _id: room.currentGameId
                                });
                                let host = (() => {
                                    if (game.result && game.result.type == Game.Result.TYPE_LIUJU) {
                                        return room.host;
                                    } else if (game.result.type == Game.Result.TYPE_JIEPAO) {
                                        if (game.result.hupais.length > 1) {
                                            return game.result.hupais[0].fangPaoUser;
                                        } else {
                                            return game.result.hupais[0].winUser;
                                        }
                                    } else if (game.result.type == Game.Result.TYPE_ZIMO) {
                                        return game.result.winUser;
                                    } else {
                                        return room.host;
                                    }
                                })();
                                //清空房间所有的牌
                                yield db.collection(g.colls.cards).deleteMany({
                                    roomNumber: room.number
                                });
                                //删除这个房间关联的game
                                yield db.collection(g.colls.games).deleteMany({
                                    roomNumber: room.number
                                });
                                //清空这个房间所有可做操作
                                yield db.collection(g.colls.actions).deleteMany({
                                    roomNumber: room.number
                                });
                                //清空这个房间所有的杠的情况
                                yield db.collection(g.colls.gangs).deleteMany({
                                    roomNumber: room.number
                                });
                                //清空过手信息
                                yield db.collection(g.colls.guoShous).deleteMany({
                                    roomNumber: room.number
                                });
                                //清空用户报听信息
                                yield db.collection(g.colls.users).updateMany({
                                    roomNumber: room.number
                                }, {
                                    $unset: {
                                        isBaoTing: 0
                                    }
                                });
                                //开始新的一局
                                let notifies = yield newGame(room, allUsers, null, allCards, host);
                                return Promise.resolve({
                                    notifies: notifies
                                });
                            } else {
                                //标记为已经准备
                                yield db.collection(g.colls.users).updateOne({
                                    unionid: me.unionid
                                }, {
                                    $set: {
                                        isReady: true
                                    }
                                });
                            }
                        }
                        return Promise.resolve({
                            to: me.unionid,
                            method: "smallGameOk"
                        });
                    } finally {
                        yield seneca.actAsync({
                            role: "lock",
                            cmd: "release",
                            key: "room" + me.roomNumber,
                        });
                    }
                }),
                quikChat: co.wrap(function*(session, index) {
                    index = index | 0;
                    if (index > 20 || index < 0) {
                        return Promise.reject({
                            method: "quikChat",
                            errcode: 200,
                            info: "错误的参数，index不能小于0或者大于20"
                        });
                    }
                    let me = yield checkSession(session);
                    if (!me.roomNumber) {
                        return Promise.reject({
                            method: "quikChat",
                            errcode: 201,
                            info: "用户没有加入房间"
                        });
                    }
                    let allUsers = yield db.collection(g.colls.users).find({
                        roomNumber: me.roomNumber
                    }).toArray();
                    let others = allUsers.filter((user) => {
                        return user.unionid != me.unionid;
                    });
                    let notifies = [];
                    for (let other of others) {
                        notifies.push({
                            to: other.unionid,
                            method: "quikChat",
                            data: {
                                index,
                                from: me.unionid
                            }
                        });
                    }
                    return Promise.resolve({
                        notifies
                    });
                }),
                record: co.wrap(function*(session) {
                    let me = yield checkSession(session);
                    let records = yield db.collection(g.colls.records).find({
                        userId: me.unionid
                    }).sort({
                        _id: -1
                    }).limit(10).toArray();
                    return Promise.resolve({
                        method: "record",
                        data: {
                            records
                        }
                    });
                }),
                recordDetail: co.wrap(function*(session, recordId) {
                    let me = yield checkSession(session);
                    let recordDetails = yield db.collection(g.colls.recordDetails).find({
                        recordId: ObjectID(recordId)
                    }).toArray();
                    return Promise.resolve({
                        method: "recordDetail",
                        data: {
                            recordDetails
                        }
                    });
                }),
                setPassword: co.wrap(function*(session, newPassword) {
                    let me = yield checkSession(session);
                    let agent = yield db.collection(g.colls.agents).findOne({
                        username: me.id + ""
                    });
                    if (!agent) {
                        yield db.collection(g.colls.agents).insert({
                            username: me.id + "",
                            password: md5(newPassword, SALT)
                        });
                    } else {
                        yield db.collection(g.colls.agents).updateOne({
                            username: me.id + ""
                        }, {
                            $set: {
                                password: md5(newPassword, SALT)
                            }
                        });
                    }
                    return Promise.resolve({
                        method: "setPassword",
                        data: {
                            ok: true
                        }
                    });
                }),
                //记录用户在线数据
                logUserInfo: co.wrap(function*(onlineCount) {
                    let r = yield db.collection(g.colls.userinfos).findOne();
                    if (r) {
                        yield db.collection(g.colls.userinfos).updateOne({}, {
                            $set: {
                                onlineCount
                            }
                        });
                    } else {
                        yield db.collection(g.colls.userinfos).insertOne({
                            onlineCount
                        });
                    }
                    return Promise.resolve(true);
                }),
                //发送录音
                sendSound: co.wrap(function*(session, data) {
                    let me = yield checkSession(session);
                    if (!me.roomNumber) {
                        return Promise.reject({
                            method: "smallGameOk",
                            errcode: 200,
                            info: "用户没有加入房间"
                        });
                    }
                    let allUsers = yield db.collection(g.colls.users).find({
                        roomNumber: me.roomNumber
                    }).toArray();
                    let others = allUsers.filter((user) => {
                        return user.unionid != me.unionid;
                    });
                    let notifies = [];
                    for (let other of others) {
                        notifies.push({
                            to: other.unionid,
                            method: "sendSound",
                            data: {
                                data
                            }
                        });
                    }
                    return Promise.resolve({
                        notifies: notifies
                    });
                }),
            };
        }),
    };
})();
